#include "Precompile.h"
#include "RenderSystem.h"
#include "DX10RenderSystem.h"
#include "DX9RenderSystem.h"
#include "ModelLoader.h"

namespace Graphics{
	// Define events
	Core::Event<RenderContext*> RenderSystem::RenderingEvent;
	Core::Event<>	 			RenderSystem::PreRenderEvent;
	Core::Event<>				RenderSystem::PostRenderEvent;

	RenderSystem::RenderSystem(RenderMode &mode):m_renderMode(mode),m_modelId(0),m_loadedAndReady(false),m_sceneManagerId(0){ 
		m_frameTimer = boost::shared_ptr<boost::timer>(new boost::timer);
		m_animationManager = boost::shared_ptr<AnimationManager>(new AnimationManager);
		m_physicsSystem = Physics::PhysicsFactory::createPhysicsSystem();

		m_guiMutex = ThreadLib::ThreadFactory::spawnMutex();
		m_removeMeshQueueMutex = ThreadLib::ThreadFactory::spawnMutex();
		m_addMeshQueueMutex = ThreadLib::ThreadFactory::spawnMutex();
		m_modelHandlesMutex = ThreadLib::ThreadFactory::spawnMutex();
	}

	void RenderSystem::update(){
		PreRenderEvent.invoke();
		m_dt = m_frameTimer->elapsed();
		m_frameTimer->restart();
		m_renderContext->m_dt = m_dt;

		// Start physics calculations
		m_physicsSystem->step(0.1f);

		// Update queued loads
		updateQueues();

		// Update animation manager
		m_renderContext->m_animationManager->update(float(m_dt),m_renderContext.get());

		if(m_cameras.size() > 0){
			(*m_cameras.begin())->update(float(m_dt));
			m_materialManager->setSharedShaderVariables(*m_cameras.begin());
		}

		beginFrame();

		// Render opeque objects
		SceneManager::RenderIdList::iterator smItr;
		std::map<unsigned int,boost::shared_ptr<SceneManager>>::iterator itr = m_sceneManagers.begin();
		while(itr != m_sceneManagers.end()){
			SceneManager::RenderIdList &list = itr->second->m_renderObjects;
			smItr = list.begin();
			while(smItr != list.end()){
				if(m_models.find(smItr->first) != m_models.end())
					m_models[smItr->first]->render(m_renderContext.get(),smItr->second);
				smItr++;
			}
			itr++;
		}

		// Render SkySphere if available
		if(m_SkySphere)
			m_SkySphere->render(m_renderContext.get());

		// Draw transparent objects, TODO: back to front
		std::map<unsigned int,std::pair<std::list<unsigned int>, std::list<unsigned int>>>::iterator transItr = m_transparentModels.begin();
		while(transItr != m_transparentModels.end()){
			m_models[transItr->first]->render(m_renderContext.get(),transItr->second.second,transItr->second.first);
			transItr++;
		}
		m_transparentModels.clear();

		RenderingEvent.invoke(m_renderContext.get());

		// Call script functions
		std::map<LuaPlus::LuaState*,std::list<std::string>>::iterator scriptItr = m_renderUpdateFunctions.begin();
		std::list<std::string>::iterator listItr;
		while(scriptItr != m_renderUpdateFunctions.end()){
			listItr = scriptItr->second.begin();
			while(listItr != scriptItr->second.end()){
				scriptItr->first->DoString((*listItr+"("+Utility::StringConverter::toString(float(m_dt))+")").c_str());

				listItr++;
			}
			scriptItr++;
		}

		endFrame();
	}

	void RenderSystem::resetDevice(){
		GraphicsFactory::m_renderContext = m_renderContext.get();
	}

	void RenderSystem::cleanup(){ 
		m_GUIManagers.clear();
		m_luaSkySpheres.clear();
		m_SkySphere.reset();
		m_models.clear();
		m_materialManager.reset();
	}

	void RenderSystem::updateQueues(){
		m_modelHandlesMutex->lock();
		std::map<unsigned int, boost::shared_ptr<Loading::LoadingHandle<Model>>>::iterator mhItr = m_modelHandles.begin();
		while(mhItr != m_modelHandles.end()){
			if(mhItr->second->m_handle){
				m_models[mhItr->first] = mhItr->second->m_handle;
				m_modelsNameToIdMap[mhItr->second->m_handle->getName()] = mhItr->first;

				mhItr = m_modelHandles.erase(mhItr);
			}

			if(mhItr != m_modelHandles.end())
				mhItr++;
		}
		m_modelHandlesMutex->unlock();

		m_addMeshQueueMutex->lock();
		std::vector<MeshPtr> tmpQueue = m_addMeshQueue;
		m_addMeshQueue.clear();
		m_addMeshQueueMutex->unlock();
		std::vector<MeshPtr>::iterator mItr = tmpQueue.begin();
		while(mItr != tmpQueue.end()){
			addMesh(*mItr);
			mItr++;
		}

		m_removeMeshQueueMutex->lock();
		tmpQueue = m_removeMeshQueue;
		m_removeMeshQueue.clear();
		m_removeMeshQueueMutex->unlock();
		mItr = tmpQueue.begin();
		while(mItr != tmpQueue.end()){
			removeMesh(*mItr);
			mItr++;
		}
	}

	LuaPlus::LuaObject RenderSystem::getSceneManager_luaWrap(LuaPlus::LuaObject sceneManagerId){
		if(!sceneManagerId.IsInteger()){
			LOG_ERROR("Invalid parameter type.");
		}

		int sceneManager = sceneManagerId.GetInteger();
		LuaPlus::LuaState* state = sceneManagerId.GetState();

		SceneManagerPtr sceneMgrPtr = Graphics::GraphicsFactory::m_renderContext->m_renderSystem->getSceneManager(sceneManager);

		LuaPlus::LuaObject object = state->BoxPointer(sceneMgrPtr.get());
		object.SetMetaTable(state->GetGlobal("SceneManagerMetaTable"));

		return object;
	}

	unsigned int RenderSystem::loadModel_luaWrap(LuaPlus::LuaObject filename){
		if(!filename.IsString()){
			LOG_ERROR("Invalid parameter type.");
			assert(0);
			return 0;
		}

		boost::shared_ptr<Loading::ModelLoader> loader = boost::shared_ptr<Loading::ModelLoader>(new Loading::ModelLoader);
		loader->setModelFilename(std::string(filename.GetString()));
		
		//Graphics::ModelPtr model = loader.load();
		boost::shared_ptr<Loading::LoadingHandle<Graphics::Model>> modelAsync = loader->loadAsync();

		int modelId = addModel(modelAsync);
		m_luaStateModels[filename.GetState()].insert(modelId);
		
		return modelId;
	}

	void RenderSystem::setSkyBox_luaWrap(	LuaPlus::LuaObject top, LuaPlus::LuaObject bottom, 
											LuaPlus::LuaObject left, LuaPlus::LuaObject right, 
											LuaPlus::LuaObject front, LuaPlus::LuaObject back){
		if(!(top.IsString()&&bottom.IsString()&&left.IsString()&&right.IsString()&&front.IsString()&&back.IsString())){
			LOG_ERROR("Invalid parameter type.");
			assert(0);
			return;
		}

		m_luaSkySpheres[top.GetState()] = SkySpherePtr(new SkySphere(	std::string(top.GetString()),std::string(bottom.GetString()),
																		std::string(left.GetString()),std::string(right.GetString()),
																		std::string(front.GetString()),std::string(back.GetString())));
		setSkySphere(m_luaSkySpheres[top.GetState()]);
	}

	void RenderSystem::unloadModel_luaWrap(LuaPlus::LuaObject modelId){
		if(!modelId.IsInteger()){
			LOG_ERROR("Invalid parameter type.");
			assert(0);
			return;
		}

		std::map<LuaPlus::LuaState*,std::set<int>>::iterator itr;
		if((itr = m_luaStateModels.find(modelId.GetState())) != m_luaStateModels.end()){
			itr->second.erase(modelId.GetInteger());
		}
	}

	void RenderSystem::registerRenderUpdateFunction_luaWrap(LuaPlus::LuaObject funcName){
		if(!funcName.IsString()){
			LOG_ERROR("Invalid parameter value type.");
			assert(0);
			return;
		}

		m_renderUpdateFunctions[funcName.GetState()].push_back(std::string(funcName.GetString()));
	}

	void RenderSystem::unregisterRenderUpdateFunction_luaWrap(LuaPlus::LuaObject funcName){
		if(!funcName.IsString()){
			LOG_ERROR("Invalid parameter value type.");
			assert(0);
			return;
		}

		std::string name = funcName.GetString();

		std::map<LuaPlus::LuaState*,std::list<std::string>>::iterator stateItr = m_renderUpdateFunctions.find(funcName.GetState());
		if(stateItr == m_renderUpdateFunctions.end()){
			LOG_ERROR("No funstions registered for this lua state.");
			assert(0);
			return;
		}

		std::list<std::string>::iterator itr = stateItr->second.begin();
		while(itr != stateItr->second.end()){
			if(itr->compare(name) == 0){
				stateItr->second.erase(itr);
				break;
			}
			itr++;
		}
	}

	void RenderSystem::unregisterLuaState(LuaPlus::LuaState* state){
		// Remove update functions
		{
			std::map<LuaPlus::LuaState*,std::list<std::string>>::iterator itr = m_renderUpdateFunctions.find(state);
			if(itr == m_renderUpdateFunctions.end()){
				return;
			}

			m_renderUpdateFunctions.erase(itr);
		}

		// Purge scenemanagers from changes made by state
		{
			std::map<unsigned int,SceneManagerPtr>::iterator itr = m_sceneManagers.begin();
			while(itr != m_sceneManagers.end()){
				itr->second->purgeLuaState(state);
				itr++;
			}
		}

		// Remove models added by state
		{
			std::map<LuaPlus::LuaState*,std::set<int>>::iterator itr;
			if((itr = m_luaStateModels.find(state)) != m_luaStateModels.end()){
				std::set<int>::iterator sItr;
				sItr = itr->second.begin();
				while(sItr != itr->second.end()){
					removeModel(*sItr);
					sItr++;
				}
				m_luaStateModels.erase(itr);
			}
		}

		// Remove skyspheres added
		{
			if(m_luaSkySpheres[state] == m_SkySphere){
				m_SkySphere.reset();
			}
			m_luaSkySpheres.erase(state);
		}
	}

	unsigned int RenderSystem::addModel(boost::shared_ptr<Model> model){
		std::map<std::string,unsigned int>::iterator itr = m_modelsNameToIdMap.find(model->getName());
		if(itr != m_modelsNameToIdMap.end()){
			LOG_ERROR("RenderSystem::addModel - Trying to add a model with the same name as another existing model, not allowed!");
			assert(0);
			return 0;
		}

		unsigned int id = m_modelId++;
		model->m_id = id;
		m_modelsNameToIdMap[model->getName()] = id;
		m_models[id] = model;
		return id;
	}

	unsigned int RenderSystem::addModel(boost::shared_ptr<Loading::LoadingHandle<Model>> modelHandle){
		unsigned int id = m_modelId++;
		m_modelHandlesMutex->lock();
		m_modelHandles[id] = modelHandle;
		m_modelHandlesMutex->unlock();
		return id;
	}
	
	void RenderSystem::removeModel(unsigned int id){
		std::map<unsigned int, ModelPtr>::iterator itr = m_models.find(id);
		if(itr == m_models.end()){
			return;
		}

		m_modelsNameToIdMap.erase(itr->second->getName());
		m_models.erase(id);
	}

	ModelPtr RenderSystem::getModel(unsigned int id){
		std::map<unsigned int, ModelPtr>::iterator itr = m_models.find(id);
		if(itr == m_models.end()){
			return ModelPtr();
		}

		ModelPtr model;
		model = itr->second;
		return model;
	}

	void RenderSystem::addMesh(boost::shared_ptr<Mesh> mesh){
		// Check model should be loaded for mesh
		if(mesh->getModelId() == -1){
			std::map<std::string,unsigned int>::iterator mItr;
			mItr = m_modelsNameToIdMap.find(Core::GeneralSettings::findFilePath(mesh->m_modelFile));

			if(mItr != m_modelsNameToIdMap.end()){
				mesh->m_modelId = mItr->second;
			}
			else{
				// Load model
				boost::shared_ptr<Loading::ModelLoader> loader = boost::shared_ptr<Loading::ModelLoader>(new Loading::ModelLoader);
				loader->setModelFilename(mesh->m_modelFile);

				boost::shared_ptr<Loading::LoadingHandle<Graphics::Model>> modelAsync = loader->loadAsync();
				mesh->m_modelId = addModel(modelAsync);
			}
		}

		ModelPtr modelPtr = getModel(mesh->getModelId());
		if(modelPtr){
			modelPtr->addInstance(mesh);
				
			// Make it update if model has an animation controller
			if(modelPtr->getControllerId() != -1){
				m_renderContext->m_animationManager->addAnimatedMeshPtr(mesh);
			}
		}
		else{
			m_addMeshQueueMutex->lock();
			m_addMeshQueue.push_back(mesh);
			m_addMeshQueueMutex->unlock();
		}
	}

	void RenderSystem::removeMesh(boost::shared_ptr<Mesh> mesh){
		ModelPtr modelPtr = getModel(mesh->getModelId());
		if(modelPtr){
			modelPtr->removeInstance(mesh);

			// Remove from animationmanager.
			if(modelPtr->getControllerId() != -1){
				m_renderContext->m_animationManager->removeAnimatedMeshPtr(mesh);
			}
		}
		else{
			m_removeMeshQueueMutex->lock();
			m_removeMeshQueue.push_back(mesh);
			m_removeMeshQueueMutex->unlock();
		}
	}

	int RenderSystem::registerSceneManager(SceneManagerPtr manager){
		manager->setRenderSystem(this);
		unsigned int id = m_sceneManagerId++;
		m_sceneManagers[id] = manager;
		m_sceneManagersRevMap[manager] = id;
		return id;
	}

	void RenderSystem::unregisterSceneManager(SceneManagerPtr manager){
		std::map<SceneManagerPtr,unsigned int>::iterator itr = m_sceneManagersRevMap.find(manager);
		if(itr == m_sceneManagersRevMap.end()){
			LOG_ERROR("Scene manager does not exist.");
			assert(0);
		}

		unregisterSceneManager(itr->second);
		m_sceneManagersRevMap.erase(itr);
	}

	void RenderSystem::unregisterSceneManager(unsigned int id){
		std::map<unsigned int,SceneManagerPtr>::iterator itr = m_sceneManagers.find(id);
		if(itr == m_sceneManagers.end()){
			LOG_ERROR("Scene manager with that id does not exist.");
			assert(0);
		}

		m_sceneManagers.erase(id);
	}

	SceneManagerPtr	RenderSystem::getSceneManager(unsigned int id){
		std::map<unsigned int,SceneManagerPtr>::iterator itr = m_sceneManagers.find(id);
		if(itr == m_sceneManagers.end()){
			LOG_ERROR("Scene manager with that id does not exist.");
			assert(0);
			return SceneManagerPtr();
		}

		return itr->second;
	}

	void RenderSystem::registerCamera(ICameraPtr camera){
		m_cameras.insert(camera);
	}

	void RenderSystem::unregisterCamera(ICameraPtr camera){
		m_cameras.erase(camera);
	}

	void RenderSystem::registerGUIManager(GUIManagerPtr gui){
		m_guiMutex->lock();
		m_GUIManagers.insert(gui);
		m_guiMutex->unlock();
	}

	void RenderSystem::unregisterGUIManager(GUIManagerPtr gui){ 
		m_guiMutex->lock();
		m_GUIManagers.erase(gui);
		m_guiMutex->unlock(); 
	}
}